package classificator.data;

import classificator.database.ClassTypeException;
import classificator.database.DbAccess;
import classificator.database.QUERY_TYPE;
import classificator.database.TableData;
import classificator.database.TableData.TupleData;
import classificator.database.TableSchema;
import classificator.database.TableSchema.Column;

import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

/**Modella l'insieme di esempi di training
 * 
 * @author 467644
 */
public class Data {
    //Matrice nXm di tipo Object che contiene il training set. 
    //Il training set è organizzato come (numero di esempi) X (numero di attributi)
    private Object data [][];
    //Cardinalità del training set
    private int numberOfExamples;
    //Array di oggetti di tipo Attribute per rappresentare gli attributi indipendenti
    private List<Attribute> explanatorySet = new LinkedList<Attribute>();
    //Oggetto per modellare l'attributo di classe (target attribute). 
    //L'attributo di classe è un attributo discreto.
    private DiscreteAttribute classAttribute;
    
    /**costruttore di classe.
     */
    public Data(String table) throws DataException{
        try {
            DbAccess.initConnection();
            TableSchema ts = new TableSchema(table);
            TableData td = new TableData();
            int maxAttrib = ts.getNumberOfAttributes();
            List<Object> distinct;
            for (int c=0; c<maxAttrib-1; c++){
                //avvaloro la lista explanatorySet
                Column col = ts.getColumn(c);
                if (col.isNumber())
                    explanatorySet.add(new ContinuousAttribute(col.getColumnName(), c));
                else {
                    distinct = td.getColumnValues(table, col, QUERY_TYPE.DISTINCT);
                    explanatorySet.add(new DiscreteAttribute(col.getColumnName(), c, distinct.toArray(new String[0])));
                }
            }
            //avvaloro classAttribute
            Column col = ts.getColumn(maxAttrib-1);
            distinct = td.getColumnValues(table, col, QUERY_TYPE.DISTINCT);
            classAttribute = new DiscreteAttribute(col.getColumnName(), maxAttrib-1, distinct.toArray(new String[0]));
            
            List<TupleData> list = td.getTransazioni(table);
            data = new Object [list.size()][ts.getNumberOfAttributes()];
            for (int r=0; r<list.size(); r++){
                TupleData tupla = list.get(r);
                for (int c=0; c<tupla.size(); c++){
                    if (!ts.getColumn(c).isNumber())
                        data[r][c] = tupla.get(c);
                    else
                        data[r][c] = new Float(tupla.get(c).toString());
                }
            }
            //avvaloro numero esempi
            numberOfExamples = data.length;
        } catch (ClassTypeException ex) {
            System.err.println(ex.getMessage());
        } catch (SQLException ex) {
            throw new DataException(ex);
        }
    }
    
    /**Restituisce il valore del membro numberOfExamples
     * 
     * @return Cardinalità dell'insieme di esempi
     */
    public int getNumberOfExamples(){
        return numberOfExamples;
    }
    
    /**Restituisce la lunghezza dell'array explanatorySet[]
     * 
     * @return Cardinalità dell'insieme degli attributi indipendenti
     */
    public int getNumberOfExplanatoryAttributes(){
        return explanatorySet.size();
    }

    /**Restituisce il valore dell'attributo di classe per l'esempio exampleIndex
     * 
     * @param exampleIndex indice di riga per la matrice data[][] per uno specifico esempio
     * @return valore dell'attributo di classe per l'esempio indicizzato in input
     */
    public String getClassValue(int exampleIndex){
        return (String) data[exampleIndex][classAttribute.getIndex()];
    }
    
    /**Restituisce il valore dell' attributo indicizzato da attributeIndex per l'esempio exampleIndex
     * 
     * ??? Input: indice di riga per la matrice data [][] per uno specifico esempio ???
     * @param exampleIndex
     * @param attributeIndex
     * 
     * @return Object associato all'attributo indipendente per l'esempio indicizzato in input
     */
    public Object getExplanatoryValue(int exampleIndex, int attributeIndex){
        return data[exampleIndex][attributeIndex];
    }
     
    /**Restituisce l'attributo indicizzato da index in explanatorySet[]
     * 
     * @param index indice nell'array explanatorySet[] per uno specifico attributo indipendente
     * 
     * @return oggetto Attribute indicizzato da index
     */
    public Attribute getExplanatoryAttribute(int index){
        return explanatorySet.get(index);
    }

    /**restituisce l'oggetto corrispondente all'attributo di classe
     * 
     * @return Oggetto DiscreteAttribute associato al membro classAttribute
     */   
    public DiscreteAttribute getClassAttribute(){
        return classAttribute;
    }
    
    /**legge i valori di tutti gli attributi per ogni esempio da data [][] e li concatena in 
     * un oggetto String che restituisce come risultato finale in forma di sequenze di testi.
     * 
     * @return sequenza di testi
     */
    @Override
    public String toString(){
        String text = new String();
        for (int i=0; i<numberOfExamples; i++){
            for (int j=0; j<5; j++){
                text+=data[i][j];
                if (j<4)
                    text+=", ";
            }
            text+="\n";
        }
        return text;
    }

    /**Ordina il sottoinsieme di esempi compresi nell'intervallo [inf,sup] in data[][] 
     * rispetto allo specifico attributo attribute
     * Usa l'Algoritmo quicksort per l'ordinamento di un array di interi usando come 
     * relazione d'ordine totale "&lt;="
     * L'array, in questo caso, è dato dai valori assunti dall'attributo passato in input.
     * 
     * @param attribute Attributo i cui valori devono essere ordinati
     * @param beginExampleIndex
     * @param endExampleIndex 
     */
    public void sort(Attribute attribute, int beginExampleIndex, int endExampleIndex){
        quicksort(attribute, beginExampleIndex, endExampleIndex);
    }

    /**Algoritmo quicksort per l'ordinamento di un array di interi A
     * usando come relazione d'ordine totale "&lt;="
     * 
     * @param A
    */
    private void quicksort(Attribute attribute, int inf, int sup){
        int pos;
        if (sup >= inf){
            if (attribute instanceof DiscreteAttribute)
                pos = partition((DiscreteAttribute)attribute, inf, sup);
            else
                pos = partition((ContinuousAttribute)attribute, inf, sup);
            if ((pos-inf) < (sup-pos+1)) {
                quicksort(attribute, inf, pos-1); 
                quicksort(attribute, pos+1,sup);
            } else {
                quicksort(attribute, pos+1, sup); 
                quicksort(attribute, inf, pos-1);
            }
        }
    }

    /**Partiziona il vettore rispetto all'elemento x e restituisce il punto di separazione
     * 
     * @param attribute attributo discreto
     * @param inf limite inferiore
     * @param sup limite superiore
     * 
     * @return punto di separazione
     */
    private int partition(DiscreteAttribute attribute, int inf, int sup){
        int i,j;
        i = inf; 
        j = sup; 
        int med = (inf + sup)/2;
        String x = (String) getExplanatoryValue(med, attribute.getIndex());
        swap(inf, med);
        while (true) {
            while(i<=sup && ((String)getExplanatoryValue(i, attribute.getIndex())).compareTo(x)<=0)
                i++; 
            while(((String)getExplanatoryValue(j, attribute.getIndex())).compareTo(x)>0)
                j--;
            if(i<j) 
                swap(i,j);
            else 
                break;
        }
        swap(inf,j);
        return j;
    }
    
    /**Partiziona il la porzione di training set nel punto medio dei valori dell'attributo passato 
     * e restituisce il punto di separazione
     * 
     * @param attribute attributo continuo
     * @param inf limite inferiore
     * @param sup limite superiore
     * 
     * @return punto di separazione
     */
    private int partition(ContinuousAttribute attribute, int inf, int sup) {
        int i,j;
        i = inf; 
        j = sup; 
        int med = (inf + sup)/2;
        Float x = (Float)getExplanatoryValue(med, attribute.getIndex());
        swap(inf, med);
        while (true) {
            while(i<=sup && ((Float)getExplanatoryValue(i, attribute.getIndex()))<=x){ 
                i++; 
            }
            while(((Float)getExplanatoryValue(j, attribute.getIndex()))>x) {
                j--;
            }
            if(i<j)
                swap(i,j);
            else 
                break;
        }
        swap(inf, j);
        return j;
    }
    
    /** scambio esempio I con esempio J
     */
    private void swap(int i,int j){
        Object temp;
        for (int k=0; k<getNumberOfExplanatoryAttributes()+1; k++){
            temp = data[i][k];
            data[i][k] = data[j][k];
            data[j][k] = temp;
        }
    }

    /**restituisce l'explanatoryset come lista di attribute
     * 
     * @return explanatoryset 
     */
    public List<Attribute> getExplanatorySet() {
        return explanatorySet;
    }

    /**imposta la lista di explanatoryset
     * 
     * @param explanatorySet explanatoryset che deve essere impostato
     */
    public void setExplanatorySet(List<Attribute> explanatorySet) {
        this.explanatorySet = explanatorySet;
    }
    
    /**Consente il test delle classi implementate, 
     * in particolare permette la stampa degli esempi ordinati per valori di attributo
     * 
     * @param args 
     */
    /*
    public static void main(String args[]){
        try {
            Data trainingSet=new Data();
            //System.out.println(trainingSet);
            System.out.println("ORDER BY "+trainingSet.getExplanatoryAttribute(0));
            trainingSet.quicksort(trainingSet.getExplanatoryAttribute(0),0 , trainingSet.getNumberOfExamples()-1);
            System.out.println(trainingSet);

            System.out.println("ORDER BY "+trainingSet.getExplanatoryAttribute(1));
            trainingSet.quicksort(trainingSet.getExplanatoryAttribute(1),0 , trainingSet.getNumberOfExamples()-1);
            System.out.println(trainingSet);

            System.out.println("ORDER BY "+trainingSet.getExplanatoryAttribute(2));
            trainingSet.quicksort(trainingSet.getExplanatoryAttribute(2),0 , trainingSet.getNumberOfExamples()-1);
            System.out.println(trainingSet);

            System.out.println("ORDER BY "+trainingSet.getExplanatoryAttribute(3));
            trainingSet.quicksort(trainingSet.getExplanatoryAttribute(3),0 , trainingSet.getNumberOfExamples()-1);
            System.out.println(trainingSet);
        } catch (DataException ex) {
            System.err.println(ex.getMessage());
        }
    }*/
}